# -*- coding:utf-8 -*-

"""

问题的提出

在Qt的GUI布局中，有时候需要彻底删除一个部件，释放其占用的内存，比如在一个网格布局中，在不影响布局的情况下，需要删除其中的某个部件，我们调用removeWidget()函数将其从布局中删除，这种操作下，虽然在界面上看不到了被移除的部件，但这并不说明，部件已经被删除，所占用的内存已经被释放。实际上，这些部件并没有被删除，占用的内存并没有释放。在Python里，可以采用以下三种方法来达到目的。

方法1：使用部件的deleteLater的函数

在Qt中，所有继承自QObject的类都有一个deleteLater函数，用于在适当的时候销毁对象自身。其原理是QObject.deleteLater()并不立即将对象销毁，而是向主循环发送了-个事件(event)，在主循环收到这个事件后，让所有事件都发送完一切处理好后才销毁对象,释放相应的内存。这样做的好处是可以在这些延迟删除的时间内完成一些操作，而且就算调用多次的deletelater也是安全的。坏处就是内存释放会不及时。

方法2：使用sip.delete函数删除部件

导入sip库

import PyQt5.sip

使用其delete函数来删除部件对象

sip.delete(widget)

这种方法和C++中使用delete删除对象的原理是一样的。

这种方法的好处是，立即删除对象和释放内存，坏处是，可能会导致不可预知的崩溃现象。

Qt中不建议使用手动使用delete来释放QObject对象，原因有二：

不注意父子关系可能或导致某个对象释放两次，一次是手动释放，一次是parent释放。
删除一个pending events等待传递的QObject会导致崩溃，所以不能直接跨线程删除对象，而QObject析构函数会断开所有信号和槽，因此用deleteLater代替比较好，它会让所有事件都发送完一切处理好后马上清除这片内存，而且就算调用多次的deletelater也是安全的。
如果没有上述两种原因存在，可以直接使用delete来删除对象和释放内存空间。

方法3：使用del函数

python提供了del 删除变量的方式，del 删除变量的原理是，它使变量的引用计数减一，如果引用计数为0，就会被回收。这样也可以达到目的，其使用方式同方法2，效果和方法2也是一样的。

测试代码

测试代码演示使用按钮，添加一些标签，可以选择用两种不同的方式来删除添加的标签，右边打印出操作后的信息，可以看到delteLater 函数并不马上执行删除并释放空间的操作，而sip.delete则马上执行删除操作并释放组件所占用的内存空间。完整测试代码如下:
"""

import sys
from common.import_pyside import *


class DemoWidgetDelete(QMainWindow):
    def __init__(self, parent=None):
        super(DemoWidgetDelete, self).__init__(parent)

        # 设置窗口标题
        self.setWindowTitle('实战PySide6: 演示彻底删除部件')
        # 设置窗口大小
        self.resize(480, 300)

        self.initUi()

    def initUi(self):

        mainLayout = QVBoxLayout()

        self.labelList = []

        mainWidget = QWidget()
        mainWidget.setLayout(mainLayout)
        self.setCentralWidget(mainWidget)

        # 三个按钮
        btnAdd = QPushButton('添加')
        btnAdd.clicked.connect(self.onButtonAdd)
        btnRemove1 = QPushButton('移除(deleteLater)')
        btnRemove1.clicked.connect(self.onButtonRemove1)
        btnRemove2 = QPushButton('移除(sip.delete)')
        btnRemove2.clicked.connect(self.onButtonRemove2)
        hLayout = QHBoxLayout()
        hLayout.addWidget(btnAdd)
        hLayout.addWidget(btnRemove1)
        hLayout.addWidget(btnRemove2)

        self.gLayout = QGridLayout()
        self.gLayout.setSpacing(10)
        widLeft = QWidget()
        widLeft.setLayout(self.gLayout)

        self.txtShow = QPlainTextEdit()
        splitter = QSplitter()
        splitter.addWidget(widLeft)
        splitter.addWidget(self.txtShow)
        splitter.addWidget(self.txtShow)
        splitter.setStretchFactor(0, 1)

        mainLayout.addLayout(hLayout)
        mainLayout.addWidget(splitter)
        # mainLayout.addStretch(Qt.Vertical)

    def onButtonAdd(self):
        addPos = self.gLayout.count() + 1
        addNum = len(self.labelList)
        self.labelList.append(QLabel('标签 ' + str(addPos)))
        self.gLayout.addWidget(self.labelList[addNum], addPos, 0)

    # 方法1
    def onButtonRemove1(self):
        self.txtShow.clear()
        self.txtShow.appendPlainText('deleteLater:')
        for idx in range(self.gLayout.count() + 1, 1, -1):
            # print(self.gLayout.rowCount(), len(self.gLayout), self.gLayout.count())
            self.txtShow.appendPlainText(
                '{},{}'.format(self.gLayout.rowCount(),  self.gLayout.count()))

            wid = self.gLayout.itemAtPosition(idx - 1, 0).widget()
            wid.deleteLater()

        # 尝试继续访问
        for idx in range(self.gLayout.count() + 1, 1, -1):
            # print(idx, self.gLayout.itemAtPosition(idx - 1, 0).widget().size())
            self.txtShow.appendPlainText('{},{}'.format(idx, self.gLayout.itemAtPosition(idx - 1, 0).widget().size()))

    # 方法2
    def onButtonRemove2(self):
        self.txtShow.clear()
        # self.txtShow.appendPlainText('sip.delete:')
        # for idx in range(self.gLayout.count() + 1, 1, -1):
        #     # print(self.gLayout.rowCount(), len(self.gLayout), self.gLayout.count())
        #     self.txtShow.appendPlainText(
        #         '{},{}'.format(self.gLayout.rowCount(), self.gLayout.count()))
        #
        #     wid = self.gLayout.itemAtPosition(idx - 1, 0).widget()
        #     self.gLayout.removeWidget(wid)
        #     sip.delete(wid)
        #
        # # 尝试继续访问
        # for idx in range(self.gLayout.count() + 1, 1, -1):
        #     # print(idx, self.gLayout.itemAtPosition(idx - 1, 0).widget().size())
        #     self.txtShow.appendPlainText('{},{}'.format(idx, self.gLayout.itemAtPosition(idx - 1, 0).widget().size()))


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = DemoWidgetDelete()
    window.show()
    sys.exit(app.exec())
